﻿Imports System.Reflection
Imports System.Text.RegularExpressions

''' <summary>
''' エンティティ定義
''' </summary>
''' <remarks></remarks>
Public Class EntityDefinition

    Public Shared Event OnEntityNameBuiding As EventHandler(Of EntityNameBuildingEventAtgs)

    Public Shared Function Create(entityType As Type) As EntityDefinition
        If entityType Is Nothing Then Throw New ArgumentNullException("entityType")

        Dim c As New EntityDefinition
        c.EntityType = entityType

        'ルート判定
        c.IsRoot = EntityDefinition.GetIsRoot(entityType)

        'プロパティ情報キャッシュ
        Dim q = EntityDefinition.GetProperties(entityType)

        For Each lprops In (From el In q Select el.ToLiteralProperties)
            For Each lprop In lprops
                c.LiteralProperties.Add(lprop)
            Next
        Next
        For Each item In (From el In q Where el.IsObjectValue)
            c.UpperProperties.Add(item)
        Next
        For Each item In (From el In q Where el.IsCollectiontValue)
            c.LowerProperties.Add(item)
        Next

        'キープロパティキャッシュ
        For Each item In (From el In c.LiteralProperties Where el.IsKey)
            c.KeyProperties.Add(item)
        Next

        Dim e As New EntityNameBuildingEventAtgs With {.EntityType = entityType, .EntityName = entityType.Name}
        c.RaiseEntityNameBuildingEvent(e)
        c.EntityName = e.EntityName

        Return c
    End Function

    Private Sub New()
    End Sub

    Private Sub RaiseEntityNameBuildingEvent(e As EntityNameBuildingEventAtgs)
        RaiseEvent OnEntityNameBuiding(Me, e)
    End Sub

    Private _entityType As Type
    ''' <summary>
    ''' エンティティの型
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property EntityType As Type
        Get
            Return _entityType
        End Get
        Private Set(value As Type)
            _entityType = value
        End Set
    End Property

    Private _entityName As String
    ''' <summary>
    ''' エンティティ名
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property EntityName As String
        Get
            Return _entityName
        End Get
        Friend Set(value As String)
            _entityName = value
        End Set
    End Property

    ''' <summary>
    ''' 基底エンティティの型
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property BaseEntityType As Type
        Get
            If Me.IsRoot Then Return Nothing
            Return Me.EntityType.BaseType
        End Get
    End Property

    Private _isRoot As Boolean
    ''' <summary>
    ''' 基底エンティティかどうかを示します
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>
    ''' ダーティチェック時の戻り値に影響します。
    ''' 基底と判断された場合、それより上層のクラスで実装されたプロパティも基底判定されたクラス上にあるとされます。
    ''' </remarks>
    Public Property IsRoot As Boolean
        Get
            Return _isRoot
        End Get
        Private Set(value As Boolean)
            _isRoot = value
        End Set
    End Property

    Private _literalProperties As New List(Of LiteralProperty)
    ''' <summary>
    ''' リテラルプロパティ
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property LiteralProperties As ICollection(Of LiteralProperty)
        Get
            Return _literalProperties
        End Get
    End Property

    Private _upperProperties As New List(Of PropertyInfo)
    ''' <summary>
    ''' 上層プロパティ
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>親</remarks>
    Public ReadOnly Property UpperProperties As ICollection(Of PropertyInfo)
        Get
            Return _upperProperties
        End Get
    End Property

    Private _lowerProperties As New List(Of PropertyInfo)
    ''' <summary>
    ''' 下層プロパティ
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>子</remarks>
    Public ReadOnly Property LowerProperties As ICollection(Of PropertyInfo)
        Get
            Return _lowerProperties
        End Get
    End Property

    Private _keyProperties As New List(Of LiteralProperty)
    ''' <summary>
    ''' キープロパティ
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property KeyProperties() As ICollection(Of LiteralProperty)
        Get
            Return _keyProperties
        End Get
    End Property

#Region "Private Shared 判定関数"
    ''' <summary>
    ''' ルートかどうかを示します
    ''' </summary>
    ''' <param name="t"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function GetIsRoot(t As Type) As Boolean
        'インターフェイスの場合、自身が基底です
        If t.BaseType Is Nothing Then Return True

        '基底クラスがObjectの場合、自身が基底です
        If t.BaseType.Equals(GetType(Object)) Then Return True

        '基底クラスが継承必須の場合、自身が基底です
        If t.BaseType.IsAbstract Then Return True

        'ルート属性がある場合、自身が基底です
        If t.GetCustomAttributes(GetType(RootAttribute), False).Any Then Return True

        'それ以外は基底ではありません
        Return False
    End Function

    ''' <summary>
    ''' 対象プロパティを返します
    ''' </summary>
    ''' <param name="t"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function GetProperties(t As Type) As IEnumerable(Of PropertyInfo)
        Dim isRoot = EntityDefinition.GetIsRoot(t)

        '読み書き可、無効属性なし、自身がルートまたは自身に所属するプロパティまたはキーが対象
        Dim q = (
            From el In t.GetProperties
            Where el.CanRead AndAlso
                  el.CanWrite AndAlso
                  el.GetCustomAttributes(GetType(IgnoreArchiveAttribute), False).Any = False AndAlso
                  (isRoot OrElse el.DeclaringType.Equals(t) OrElse el.IsKey) AndAlso
                  el.ToValueType <> ValueTypes.Unknown
            Order By
                 If(el.IsKey, 1, 2)
        )

        Return q
    End Function
#End Region

End Class
